﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace NDBGen
{
    /*public class Generator
    {
        public Generator(ParsedInput input)
        {
            this.ParsedInput = input;
        }

        public ParsedInput ParsedInput { get; private set; }

        internal const String TAB_STRING = "    ";

        private static DataType[] ColumnTokens;
        private static DataType[] DataTypeHandlers;
        private static DataType[] TriggeredColumnMetadata;

        private static DataType[] TableTokens;

        public static void Init()
        {
            Generator.Init(null);
        }

        public static void Init(String pluginDir)
        {
            var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
            var loadedPaths = loadedAssemblies.Where(a => !a.ReflectionOnly).Select(a => a.Location).ToArray();

            if (!String.IsNullOrEmpty(pluginDir))
            {
                var referencedPaths = Directory.GetFiles(pluginDir, "*.dll");
                var toLoad = referencedPaths.Where(r => !loadedPaths.Contains(r, StringComparer.InvariantCultureIgnoreCase)).ToList();
                toLoad.ForEach(path => loadedAssemblies.Add(AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(path))));
            }

            Generator.ColumnTokens = Discover<IColumnToken>();
            Generator.DataTypeHandlers = Discover<IDataTypeDescriptor>();
            Generator.TriggeredColumnMetadata = Discover<ITriggeredColumnMetadata>();
            Generator.TableTokens = Discover<ITableTokenDescriptor>();
        }

        public static IColumnTokenDescriptor[] CreateColumnTokenHandlerSet()
        {
            return CreateHandlerSet<IColumnToken>(Generator.ColumnTokens);
        }

        public static IDataTypeDescriptor[] CreateDataTypeHandlerSet()
        {
            return CreateHandlerSet<IDataTypeHandler>(Generator.DataTypeHandlers);
        }

        public static ITriggeredColumnMetadata[] CreateTriggeredColumnMetadataHandlerSet()
        {
            return CreateHandlerSet<ITriggeredColumnMetadata>(Generator.TriggeredColumnMetadata);
        }

        public static ITableTokenDescriptor[] CreateTableTokenHandlerSet()
        {
            return CreateHandlerSet<ITableToken>(Generator.TableTokens);
        }

        private static TDescriptor[] CreateHandlerSet<TDescriptor>(DataType[] types)
        {
            var query =
                from t in types
                select (TDescriptor)Activator.CreateInstance(t);

            return query.ToArray();
        }

        private static DataType[] Discover<TDescriptor>()
        {
            var query =
                from a in AppDomain.CurrentDomain.GetAssemblies()
                select a.GetTypes() into types
                from t in types
                where !t.IsAbstract
                select new { DataType = t, Interfaces = t.GetInterfaces() } into t
                where t.Interfaces.Contains(typeof(TDescriptor))
                select new { DataType = t.DataType, Interfaces = t.Interfaces, IsOrdered = t.Interfaces.Any(i => i == typeof(IOrderedMetadata)) } into t
                select new { DataType = t.DataType, Interfaces = t.Interfaces, IsOrdered = t.IsOrdered, Instance = Activator.CreateInstance(t.DataType) } into t
                orderby t.IsOrdered descending, t.IsOrdered ? ((IOrderedMetadata)t.Instance).GenerationIndex : 0
                select t.DataType;

            return query.ToArray();
        }

        public List<ScriptedTableData> ParseScriptedData(String s)
        {
            var lines = new Queue<String>(
                from line in s.Split(new String[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
                where line.Trim() != String.Empty
                    && !line.StartsWith("-")
                select line);

            var defs = new List<ScriptedTableDataDefinition>();
            ScriptedTableDataDefinition item = null;
            while (lines.Any())
            {
                var line = lines.Dequeue();
                
                if (line.StartsWith("#"))
                {
                    if (item != null) defs.Add(item);
                    item = new ScriptedTableDataDefinition { TableDefLine = line, DataLines = new List<String>() };
                    continue;
                }
                else if (line.StartsWith("|"))
                {
                    item.ColumnDefLine = line;
                    continue;
                }
                else
                {
                    item.DataLines.Add(line);
                    if (!lines.Any()) defs.Add(item);
                }
            }

            var missingColumnDefLine = defs.FirstOrDefault(d => String.IsNullOrEmpty(d.ColumnDefLine));
            if (missingColumnDefLine != null) throw new ArgumentException(String.Format("{0} is missing its column definition line", missingColumnDefLine.TableDefLine));

            var result = defs.Select(d => ScriptedTableData.Parse(d)).ToList();
            this.ScriptedData.AddRange(result);
            return result;
        }

        internal static Dictionary<Program.Arg, String> GenerateSql(List<Table> tables)
        {
            var schemaOutput = new List<String>();
            foreach (var table in tables) schemaOutput.Add(table.GenerateSql());
            
            var lookupOutput = new List<String>();
            foreach (var table in tables)
            {
                lookupOutput.AddRange(table.GenerateSqlDefineProcedure(null, table.ScriptedData));
                lookupOutput.AddRange(table.WriteDefineData(null, table.ScriptedData));
            }

            var seedOutput = new List<String>();
            foreach (var table in tables)
            {
                seedOutput.AddRange(table.GenerateSqlDefineProcedure("_seed", table.SeedData));
                seedOutput.AddRange(table.WriteDefineData("_seed", table.SeedData));                
            }

            var fakeOutput = new List<String>();
            foreach (var table in tables)
            {
                fakeOutput.AddRange(table.GenerateSqlDefineProcedure("_fake", table.FakeData));
                fakeOutput.AddRange(table.WriteDefineData("_fake", table.FakeData));
            }

            var result = new Dictionary<Program.Arg, String>();
            schemaOutput.RemoveAll(l => String.IsNullOrEmpty(l));
            lookupOutput.RemoveAll(l => String.IsNullOrEmpty(l));
            seedOutput.RemoveAll(l => String.IsNullOrEmpty(l));
            fakeOutput.RemoveAll(l => String.IsNullOrEmpty(l));

            if (lookupOutput.Any())
            {
                if (Program.config.ContainsKey(Program.Arg.LookupDataOutput)) result.Add(Program.Arg.LookupDataOutput, lookupOutput.CombineLines());
                else schemaOutput.AddRange(lookupOutput);
            }

            if (seedOutput.Any())
            {
                if (Program.config.ContainsKey(Program.Arg.SeedDataOutput)) result.Add(Program.Arg.SeedDataOutput, seedOutput.CombineLines());
                else schemaOutput.AddRange(seedOutput);
            }

            if (fakeOutput.Any())
            {
                if (Program.config.ContainsKey(Program.Arg.FakeDataOutput)) result.Add(Program.Arg.FakeDataOutput, fakeOutput.CombineLines());
                else schemaOutput.AddRange(fakeOutput);
            }

            result.Add(Program.Arg.SchemaSqlOutput, schemaOutput.CombineLines());

            return result;
        }

        public static String GenerateObliterate(List<Table> tables)
        {
            tables.Reverse();
            var output = new List<String>();
            foreach (var table in tables) output.Add(table.GenerateDropSql());
            return output.CombineLines();
        }

        public static String GenerateCSharpDataContext(List<Table> tables, String rootNamespace)
        {
            var schema = tables.Select(t => t.Schema).Distinct();
            if (schema.Count() > 1) throw new ArgumentOutOfRangeException("tables", "To use this method, all tables must be in the same schema.");
            var usingNamespace = rootNamespace + "." + schema.First().ToPascalCase();

            using (var writer = Generator.CreateWriter())
            {
                writer.WriteLine("using System.Data.Linq;");
                writer.WriteLine("using " + usingNamespace + ";");
                writer.WriteLine();
                writer.WriteLine("namespace " + rootNamespace);
                writer.WriteLine("{");
                writer.Indent++;
                writer.WriteLine("public partial class DB");
                writer.WriteLine("{");
                writer.Indent++;
                foreach (var table in tables) writer.WriteLine(String.Format("        public Table<{0}> {1} {{ get {{ return this.GetTable<{0}>(); }} }}", table.TypeName, table.Name.ToPascalCase()));
                writer.Indent--;
                writer.WriteLine("}");
                writer.Indent--;
                writer.WriteLine("}");
                return ((StringWriter)writer.InnerWriter).ToString();
            }
        }
    }
    */
}
